/*
* TrainViewActionBarActivity.java
* Last modified on 04-03-2014 16:41-0400 by brianhmayo
*
* Copyright (c) 2014 SEPTA. All rights reserved.
*/
package org.septa.android.app.activities;
import android.graphics.Color;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.location.LocationClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.PolylineOptions;
import org.septa.android.app.R;
import org.septa.android.app.fragments.TrainViewListFragment;
import org.septa.android.app.fragments.TransitViewRouteViewListFragment;
import org.septa.android.app.models.KMLModel;
import org.septa.android.app.models.ObjectFactory;
import org.septa.android.app.models.servicemodels.TrainViewModel;
import org.septa.android.app.models.servicemodels.TransitViewVehicleModel;
import org.septa.android.app.services.apiproxies.TrainViewServiceProxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import retrofit.Callback;
import retrofit.RetrofitError;
import retrofit.client.Response;
public class TrainViewActionBarActivity extends BaseAnalyticsActionBarActivity implements
LocationListener,
GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener{
public static final String TAG = TrainViewActionBarActivity.class.getName();
KMLModel kmlModel;
private GoogleMap mMap;
final int RQS_GooglePlayServices = 1;
LocationClient mLocationClient;
private static final int MILLISECONDS_PER_SECOND = 1000;
public static final int UPDATE_INTERVAL_IN_SECONDS = 10;
private static final long UPDATE_INTERVAL =
MILLISECONDS_PER_SECOND * UPDATE_INTERVAL_IN_SECONDS;
private static final int FASTEST_INTERVAL_IN_SECONDS = 10;
private static final long FASTEST_INTERVAL =
MILLISECONDS_PER_SECOND * FASTEST_INTERVAL_IN_SECONDS;
private boolean listviewRevealed = false;
private ArrayList<Marker>markerList = new ArrayList<Marker>();
private Timer asyncTrainViewRefreshTimer;
private List<TrainViewModel> trainViewArrayList = new ArrayList<TrainViewModel>();
private LatLng currentLocation;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String actionBarTitleText = getIntent().getStringExtra(getString(R.string.actionbar_titletext_key));
String iconImageNameSuffix = getIntent().getStringExtra(getString(R.string.actionbar_iconimage_imagenamesuffix_key));
String resourceName = getString(R.string.actionbar_iconimage_imagename_base).concat(iconImageNameSuffix);
int id = getResources().getIdentifier(resourceName, "drawable", getPackageName());
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(actionBarTitleText);
getSupportActionBar().setIcon(id);
setContentView(R.layout.trainview);
mMap = ((SupportMapFragment)getSupportFragmentManager().
findFragmentById(R.id.trainview_map_fragment)).
getMap();
// set the initial center point of the map on Center City, Philadelphia with a default zoom
double defaultLatitute = Double.parseDouble(getResources().getString(R.string.generalmap_default_location_latitude));
double defaultLongitude = Double.parseDouble(getResources().getString(R.string.generalmap_default_location_longitude));
float defaultZoomLevel = Float.parseFloat(getResources().getString(R.string.generalmap_default_zoomlevel));
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(defaultLatitute, defaultLongitude), defaultZoomLevel));
mMap.setMyLocationEnabled(true);
mLocationClient = new LocationClient(this, this, this);
kmlModel = ObjectFactory.getInstance().getKMLModel(this, "kml/train/regionalrail.kml");
// loop through the placemarks
List<KMLModel.Document.Placemark> placemarkList = kmlModel.getDocument().getPlacemarkList();
for (KMLModel.Document.Placemark placemark : placemarkList) {
List<KMLModel.Document.MultiGeometry.LineString> lineStringList = placemark.getMultiGeometry().getLineStringList();
for (KMLModel.Document.MultiGeometry.LineString lineString : lineStringList) {
String color = "#" + kmlModel.getDocument().getColorForStyleId(placemark.getStyleUrl());
List<LatLng> latLngCoordinateList = lineString.getLatLngCoordinates();
PolylineOptions lineOptions = new PolylineOptions().addAll(latLngCoordinateList)
.color(Color.parseColor(color))
.width(3.0f)
.visible(true);
mMap.addPolyline(lineOptions);
}
}
this.fetchTrainViewData();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.trainsitview_action_bar, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
return true;
}
private void revealListView() {
final FrameLayout rl1 = (FrameLayout) findViewById(R.id.trainview_map_fragment_view);
Animation anim = AnimationUtils.loadAnimation(this, R.anim.slide_right_to_left);
anim.setAnimationListener(new Animation.AnimationListener(){
@Override
public void onAnimationStart(Animation arg0) {
final View shadowView = (View) findViewById(R.id.trainview_map_fragmet_view_shadow);
shadowView.setVisibility(View.VISIBLE);
shadowView.bringToFront();
}
@Override
public void onAnimationRepeat(Animation arg0) {
}
@Override
public void onAnimationEnd(Animation arg0) {
LinearLayout ll2 = (LinearLayout) findViewById(R.id.back_frame);
ll2.bringToFront();
}
});
anim.setInterpolator((new AccelerateDecelerateInterpolator()));
anim.setFillAfter(true);
rl1.startAnimation(anim);
}
private void hideListView() {
final FrameLayout rl1 = (FrameLayout) findViewById(R.id.trainview_map_fragment_view);
Animation anim = AnimationUtils.loadAnimation(this, R.anim.slide_left_to_right);
rl1.bringToFront();
anim.setAnimationListener(new Animation.AnimationListener(){
@Override
public void onAnimationStart(Animation arg0) {
}
@Override
public void onAnimationRepeat(Animation arg0) {
}
@Override
public void onAnimationEnd(Animation arg0) {
View shadowView = (View) findViewById(R.id.trainview_map_fragmet_view_shadow);
shadowView.setVisibility(View.INVISIBLE);
LinearLayout mapView = (LinearLayout) findViewById(R.id.trainview_map_fragment_innerview);
mapView.bringToFront();
}
});
anim.setInterpolator((new AccelerateDecelerateInterpolator()));
anim.setFillAfter(true);
rl1.startAnimation(anim);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.actionmenu_transitview_reveallistview:
if (listviewRevealed) {
listviewRevealed = false;
hideListView();
} else {
listviewRevealed = true;
revealListView();
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onLocationChanged(Location newLocation) {
// TODO: find a different way to tell if we should make our network calls, with a timer.
// TODO: find a better way to shut off the updates and resume when it makes sense
if (newLocation.getAccuracy()< getResources().getInteger(R.integer.trainview_map_accuracy_limit_in_meters)) {
mLocationClient.disconnect();
currentLocation = new LatLng(newLocation.getLatitude(), newLocation.getLongitude());
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(currentLocation,
Float.parseFloat(getString(R.string.trainviewactionbaractivity_map_zoom_level_float))));
updateTrainList();
}
}
/*
* Called by LocationModel Services when the request to connect the
* client finishes successfully. At this point, you can
* request the current location or start periodic updates
*/
@Override
public void onConnected(Bundle dataBundle) {
// Display the connection status
LocationRequest mLocationRequest = LocationRequest.create();
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequest.setInterval(UPDATE_INTERVAL);
mLocationRequest.setFastestInterval(FASTEST_INTERVAL);
mLocationClient.requestLocationUpdates(mLocationRequest, this);
}
@Override
public void onDisconnected() {
}
/*
* Called by LocationModel Services if the attempt to
* LocationModel Services fails.
*/
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
/*
* Google Play services can resolve some errors it detects.
* If the error has a resolution, try sending an Intent to
* start a Google Play services activity that can resolve
* error.
*/
if (connectionResult.hasResolution()) {
// try {
// Start an Activity that tries to resolve the error
// connectionResult.startResolutionForResult(
// this,
// 9000);
/*
* Thrown if Google Play services canceled the original
* PendingIntent
*/
// } catch (IntentSender.SendIntentException e) {
// // Log the error
// e.printStackTrace();
// }
} else {
/*
* If no resolution is available, display a dialog to the
* user with the error.
*/
Log.d(TAG, "location services error: " + connectionResult.getErrorCode());
}
}
public void asyncTrainViewRefresh() {
asyncTrainViewRefreshTimer = new Timer();
TimerTask doAsynchronousTask = new TimerTask() {
@Override
public void run() {
try {
fetchTrainViewData();
} catch (Exception e) {
// TODO Auto-generated catch block
}
}
};
int refreshInterval = getResources().getInteger(R.integer.vehicle_refresh_interval_ms);
asyncTrainViewRefreshTimer.schedule(doAsynchronousTask, 0, refreshInterval);
}
@Override
protected void onPause() {
super.onPause();
asyncTrainViewRefreshTimer.cancel();
asyncTrainViewRefreshTimer.purge();
}
@Override
protected void onStart() {
super.onStart();
// Connect the client.
mLocationClient.connect();
}
@Override
protected void onStop() {
// Disconnecting the client invalidates it.
mLocationClient.disconnect();
super.onStop();
}
@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
asyncTrainViewRefresh();
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(getApplicationContext());
if (resultCode != ConnectionResult.SUCCESS){
GooglePlayServicesUtil.getErrorDialog(resultCode, this, RQS_GooglePlayServices);
}
}
/** calculates the distance between two locations in MILES */
private double distance(double lat1, double lng1, double lat2, double lng2) {
double earthRadius = 3958.75; // in miles, change to 6371 for kilometers
double dLat = Math.toRadians(lat2-lat1);
double dLng = Math.toRadians(lng2-lng1);
double sindLat = Math.sin(dLat / 2);
double sindLng = Math.sin(dLng / 2);
double a = Math.pow(sindLat, 2) + Math.pow(sindLng, 2)
* Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2));
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
double dist = earthRadius * c;
return dist;
}
private double getDistanceFromCurrentLocation(LatLng compareToLocation) {
if (currentLocation != null) {
double currentLocationLatitude = currentLocation.latitude;
double currentLocationLongitude = currentLocation.longitude;
double compareToLocationLatitude = compareToLocation.latitude;
double compareToLocationLongitude = compareToLocation.longitude;
// lat1 and lng1 are the values of a previously stored location
return distance(currentLocationLatitude, currentLocationLongitude, compareToLocationLatitude, compareToLocationLongitude);
}
return 0;
}
private void updateTrainList() {
TrainViewListFragment listFragment = (TrainViewListFragment) getSupportFragmentManager().findFragmentById(R.id.trainview_list_fragment);
for (TrainViewModel trainViewModel: trainViewArrayList) {
trainViewModel.setDistanceFromCurrentLocation(getDistanceFromCurrentLocation(new LatLng(trainViewModel.getLatitude(), trainViewModel.getLongitude())));
}
Collections.sort(trainViewArrayList);
listFragment.setTrainViewModels(trainViewArrayList);
}
private void fetchTrainViewData() {
Callback callback = new Callback() {
@Override
public void success(Object o, Response response) {
setProgressBarIndeterminateVisibility(Boolean.FALSE);
trainViewArrayList = (ArrayList<TrainViewModel>)o;
TrainViewListFragment listFragment = (TrainViewListFragment) getSupportFragmentManager().findFragmentById(R.id.trainview_list_fragment);
// test the listFragment. If null, we may have transitioned off this view and this fetch
// was long running from either a slow start or an async timer task.
if (listFragment == null) {
return;
}
// clear all of the markers off the map
for (Marker marker : markerList) {
marker.remove();
}
markerList.clear();
for (TrainViewModel trainView: trainViewArrayList) {
BitmapDescriptor trainIcon;
trainView.setDistanceFromCurrentLocation(getDistanceFromCurrentLocation(trainView.getLatLng()));
if (trainView.isSouthBound()) {
trainIcon = BitmapDescriptorFactory.fromResource(R.drawable.ic_trainview_rrl_red);
} else {
trainIcon = BitmapDescriptorFactory.fromResource(R.drawable.ic_trainview_rrl_blue);
}
String title = "Train #" + trainView.getTrainNumber() + " ";
if (trainView.isLate()) {
title += "(" + trainView.getLate()+ " min late)";
} else {
title += "(on time)";
}
String snippet = trainView.getSource() + " to " + trainView.getDestination();
// check to make sure that mMap is not null
if (mMap != null) {
Marker marker = mMap.addMarker(new MarkerOptions()
.position(trainView.getLatLng())
.title(title)
.icon(trainIcon)
.snippet(snippet));
markerList.add(marker);
}
}
Collections.sort(trainViewArrayList);
listFragment.setTrainViewModels(trainViewArrayList);
}
@Override
public void failure(RetrofitError retrofitError) {
setProgressBarIndeterminateVisibility(Boolean.FALSE);
try {
Log.d(TAG, "A failure in the call to train view service with body |" + retrofitError.getResponse().getBody().in() + "|");
} catch (Exception ex) {
// TODO: clean this up
Log.d(TAG, "blah... what is going on?");
}
}
};
TrainViewServiceProxy trainViewServiceProxy = new TrainViewServiceProxy();
setProgressBarIndeterminateVisibility(Boolean.TRUE);
trainViewServiceProxy.getTrainView(callback);
}
}